package Image.segment;

import Common.MathUtils;
import Image.SegmentedImage;
import Training.TrainingData;
import java.awt.Point;
import java.	awt.Rectangle;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.util.Vector;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;

/**
 * This class represents each segment in an image and contains all features associated
 * with it.
 * 
 * @author Seth Wessitsh <seth@wessitsh.com>
 */
public class Segment {
	/**
	 * The image this segment belongs to
	 */
	private SegmentedImage image;
	/**
	 * The region this segment corresponds to
	 */
	private ROI region = null;
	/**
	 * The number of this segment in the image, used to get the corresponding word
	 */
	private int number = -1;
	/**
	 * The word that corresponds to this segment
	 */
	private String word = "?";
	/**
	 * The set of all features
	 */
	private Vector<Double[]> features;
	/**
	 * The unit vector for the number of features we have, used for calculating similarity
	 */
	private Vector<Double> unitVector = new Vector<Double>();
	/**
	 * Used to normalize each feature for similarity
	 */
	private Vector<Double> maximums = new Vector<Double>();
	/**
	 * The training data used to calculate data
	 */
	private TrainingData trainingData;
	/**
	 * The coefficients of variance for each feature
	 */
	private Vector<Double> cvs;
	/**
	 * The mean value for each band of this segment
	 * mean[0] = Intensity
	 * mean[1] = Hue
	 * mean[2] = Saturation
	 */
	private double[] mean;
	/**
	 * The centroid of the segment
	 */
	private Point centroid;
	/**
	 * The area of the segment in pixels
	 */
	private double area;
	/**
	 * The smallest rectangle surrounding the segment
	 */
	private Rectangle rectangle;
	/**
	 * A ratio of the area of the segment to the area of the smallest
	 * rectangle surrounding it
	 */
	private double rectangularity;
	/**
	 * The length of the perimeter of the segment
	 */
//	private int perimeter;
	/**
	 * The percentage from the left of the image
	 */
	private double left;
	/**
	 * The percentage from the top of the image
	 */
	private double top;

	public Segment(Vector<Double[]> features, SegmentedImage imageToSegment) {
		this.features = features;
		image = imageToSegment;

		unitVector.add(1.0); unitVector.add(1.0); unitVector.add(1.0);
		unitVector.add(1.0);

		/**
		 * Add the maximum value of the distance between two corresponding features for
		 * normalization purposes, -1.0 indicates that the maximum should not be used
		 */
		maximums.add(Math.PI); maximums.add(-1.0); maximums.add(-1.0);
		maximums.add(Math.PI);
	}

	/**
	 * 
	 * @param roi
	 * @param imageToSegment
	 */
	public Segment(ROI roi, SegmentedImage imageToSegment, int segNumber) {
		region = roi;
		features = new Vector<Double[]>();
		image = imageToSegment;
		number = segNumber;
		word = imageToSegment.getWord(number);

		unitVector.add(1.0); unitVector.add(1.0); unitVector.add(1.0);
		unitVector.add(1.0);

		/**
		 * Add the maximum value of the distance between two corresponding features for
		 * normalization purposes, -1.0 indicates that the maximum should not be used
		 */
		maximums.add(Math.PI); maximums.add(-1.0); maximums.add(-1.0);
		maximums.add(Math.PI);

		calculateFeatures();
	}

	/**
	 * @return the feature vector for this segment
	 */
	public Vector<Double[]> getFeatures() {
		return features;
	}

	/**
	 * If the feature is defined by a vector then we take the cosine distance between
	 * the two features.  If it's defined by a scalar we simply take the absolute value
	 * of the difference.  This results in a similarity vector which we apply the
	 * Coefficients of Variance for each feature to.  After this we take the magnitude
	 * of the resulting vector and return that as the similarity.
	 * 
	 * @param s the segment to get the similarity to
	 * @return a double indicating how similar this segment is to the one provided based
	 *          on the description above.  0 is most similar.
	 */
	public Double getSimilarity(Segment s, boolean useCV) {
		Vector<Double> v = new Vector<Double>();
		if (cvs == null) cvs = trainingData.getCVScores();


//		printFeatureVector(features);
//		printFeatureVector(s.features);

		for (int i=0; i < features.size(); i++) {
			Double[] f = features.get(i),
			         g = s.features.get(i);

			double max = maximums.get(i);
			max = max > 0 ? max : 1;

			double dist;
			if (f.length > 1) {
				dist = MathUtils.cosDist(f, g)/max;
			} else {
				dist = Math.abs( f[0] - g[0] );
			}

			v.add(dist);
		}

		// print similarity vector
		//System.out.println(v);

		/**
		 * Apply coefficients of variance to adjust feature similarity
		 */
		if (useCV) {
			for (int i=0; i < v.size(); i++) {
				v.set(i, v.get(i)*(1-cvs.get(i)));
			}
		}

		// print adjusted similarity vector
		//System.out.println(v);

		/**
		 * Find magnitude of the vector v and return this as the similarity
		 */
		return MathUtils.mag(v);
	}

	/**
	 * @return an image representing the segment
	 */
	public PlanarImage getAsImage() {
		return region.getAsImage();
	}

	/**
	 * @return the smallest rectangle surrounding the segment
	 */
	public Rectangle getBounds() {
		return region.getBounds();
	}

	/**
	 * @return the word associated with this segment, if any
	 */
	public String getWord() {
		return word;
	}

	/**
	 * @param word the word to be associated with the segment
	 */
	public void setWord(String word) {
		this.word = word;
	}

	/**
	 * @param data the training data to be used for comparison
	 */
	public void setTrainingData(TrainingData data) {
		trainingData = data;
	}

	/**
	 * @return the centroid of the segment
	 */
	public Point getCentroid() {
		return centroid;
	}

	/**
	 * Creates the feature vector for this segment
	 */
	private void calculateFeatures() {
		int imageWidth = image.getImage().getWidth(),
			imageHeight = image.getImage().getHeight();

		/**
		 * Initialize the parameters for the feature calculation
		 */
		ParameterBlock pb = new ParameterBlock();
		pb.addSource(image.getImage());
		pb.add(region);

		/**
		 * Call JAI to calculate some features.  It's called centroid but
		 * calculates other things as well.
		 */
		RenderedImage centroidImage = JAI.create("centroid", pb);

		/**
		 * Retrieve the calculated features from the image
		 */
		centroid = (Point) centroidImage.getProperty("centroid");
		area = (Integer) centroidImage.getProperty("area");
		rectangle = (Rectangle) centroidImage.getProperty("rectangle");
//		perimeter = (Integer) centroidImage.getProperty("perimeter");
		rectangularity = (double)area/(double)(rectangle.width*rectangle.height);
		left = (double)centroid.x/(double)imageWidth;
		top = (double)centroid.y/(double)imageHeight;

		/**
		 * Calculate the mean IHS for this segment
		 * its IHS because the image was converted to the IHS ColorSpace elsewhere
		 */
		pb = new ParameterBlock();
		pb.addSource(image.getImage());
		pb.add(region); pb.add(4); pb.add(4);

		/**
		 * Get the mean from the image
		 */
		RenderedImage meanImage = JAI.create("mean", pb);
		mean = (double[]) meanImage.getProperty("mean");

		/**
		 * Add the features to the vector
		 */
		features.add(new Double[] {mean[0], mean[1], mean[2]});
		features.add(new Double[] {area/(imageWidth*imageHeight)});
		features.add(new Double[] {rectangularity});
//		features.add(new Double[] {(perimeter*perimeter)/area}); // roundness
		features.add(new Double[] {left, top});
	}

	/**
	 * Used for debugging purposes
	 */
	public static void printFeatureVector(Vector<Double[]> v) {
		System.out.print("<");
		for (Double[] da : v) {
			System.out.print("[");
			for (Double d : da) {
				System.out.print(d + ",");
			}
			System.out.print("],");
		}
		System.out.println(">");
	}
}
